/**
 * @(#)PursuitGraphicsPanel.java
 *
 *
 * @author Duyun Chen
 * @version 1.00 2008/10/3
 */
 
package Pursuit_Client;

import javax.swing.*;
import java.awt.*;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;

public class PursuitGraphicsPanel2 extends JPanel implements Runnable {

	private static final long serialVersionUID = 1921342839119914520L;
	private Thread thread;
	private Rectangle bounds = new Rectangle(0,0,400,400);

    private int gridXResolution = 10;
    private int gridYResolution = 10;
    
    //controls "frame rate" => wait time between each redraw
	private final int DELAY = 5;
	
    //Colors of respective algorithms
	private Color yourColor = Color.RED;
	private Color hisColor = Color.BLUE;
	
	//how many pixels away can the algorithm be from an edge before
	//invoking a graphics transformation?
	private int edgeTolerance = 10;
	
	//Stores every unique point sent from server for both algorithms
	private ArrayList<Point2D> yourPoints = new ArrayList<Point2D>();
	private ArrayList<Point2D> hisPoints = new ArrayList<Point2D>();
	
	//Stores starting points for both players
	private Point2D yourStartPoint = null;
	private Point2D hisStartPoint = null;	
	
	//stops the graphics panel from repainting
    private boolean StopFlag = false;
    
    //clear the graphics panel if set to true. After clear immediately set to false.
    private boolean CLEAR = true;

	
    public PursuitGraphicsPanel2(PursuitClient c) 
    {
    	super();
    	
    	setPreferredSize(bounds.getSize());
    	setBorder(BorderFactory.createLoweredBevelBorder());
    	setBackground(Color.white);

    	thread = new Thread(this);
    }
    
    public Dimension getPreferredSize() 
    { 	return bounds.getSize();   }
    
    public void begin() 
    {
    	if (!(thread.isAlive())) 
    			thread.start();
    	else 
    	{
    		StopFlag = false;
    		CLEAR = true;
    	}
    }
    
    public void stop() 
    {
    	PursuitClient.print("Graphics stopped");
    	StopFlag = true;    
    }
    
    public boolean isActive() 
    {  	return thread.isAlive() && !StopFlag;   }
    

	public void run() 
    {
    	while (true) 
    	{
    		try 
    		{ Thread.sleep(DELAY);	}
    		catch (InterruptedException ie) 
    		{	PursuitClient.print("Graphics Terminated (interrupt)");	}
    		
    		repaint();
    	}
    }
    
    @SuppressWarnings("unchecked")
	public synchronized void paintComponent(Graphics g) 
    {
    	//if (StopFlag) 
    	//	return;
    	
    	g.setColor(Color.LIGHT_GRAY);
		//g.fillRect(0,0,(int)bounds.getWidth(),(int)bounds.getHeight());

		drawGrid(g);
		
		//if at most one point present, don't attempt to draw shit
		if (yourPoints.size() <= 1 || hisPoints.size() <= 1) return;
		
		ArrayList<Point2D> yourDrawPoints = (ArrayList<Point2D>)yourPoints.clone();
		ArrayList<Point2D> hisDrawPoints = (ArrayList<Point2D>)hisPoints.clone();		

		Iterator<Point2D> yourIterator = yourDrawPoints.iterator();
		Iterator<Point2D> hisIterator = hisDrawPoints.iterator();
		Point2D myOldPoint = yourStartPoint;
		Point2D hisOldPoint = hisStartPoint;
		
		ArrayList<Point2D> allPoints = new ArrayList<Point2D>(yourDrawPoints);
		allPoints.addAll(hisDrawPoints);
		
    	Point2D[] extremePoints = 	{    	//get extreme points
			getExtremePoint(allPoints, true, true), //point with highest x
			getExtremePoint(allPoints, true, false),//point with lowest x
			getExtremePoint(allPoints, false, true),//point with highest y
			getExtremePoint(allPoints, false, false)//point with lowest y
		};
    	System.err.println("max x1: " + bounds.getMaxX());
    	System.err.println("max y1: " + bounds.getMaxY());
    	System.err.println("min x1: " + bounds.getMinX());
    	System.err.println("min y1: " + bounds.getMinY());   
    	
    	Rectangle newBounds = new Rectangle(
    								(int)Math.min(extremePoints[1].getX(), bounds.getMinX()),
    								(int)Math.min(extremePoints[3].getY(), bounds.getMinY()),
    								(int)Math.max(extremePoints[0].getX(), bounds.getMaxX()) -
    								(int)Math.min(extremePoints[1].getX(), bounds.getMinX()),
    								(int)Math.max(extremePoints[2].getY(), bounds.getMaxY()) -
    								(int)Math.min(extremePoints[3].getY(), bounds.getMinY())
    							);
    	
    	bounds.setRect(newBounds);
    	setBounds(bounds);
    	if(!StopFlag) PursuitClient.getGraphicsScrollpane().getViewport().setViewPosition(new Point(
    				(int)(yourPoints.get(yourPoints.size()-1).getX() + bounds.getWidth()/2),
    				(int)(yourPoints.get(yourPoints.size()-1).getY() + bounds.getHeight()/2))
    	);
    	if(!StopFlag) PursuitClient.getGraphicsScrollpane().getViewport().setViewPosition(new Point(0,0));
    	System.err.println("max x: " + newBounds.getMaxX());
    	System.err.println("max y: " + newBounds.getMaxY());
    	System.err.println("min x: " + newBounds.getMinX());
    	System.err.println("min y: " + newBounds.getMinY());   
    	
		g.setColor(yourColor);
		while (yourIterator.hasNext()) {
			Point2D myNewPoint = yourIterator.next();
			drawLine(g, myOldPoint, myNewPoint);
			myOldPoint = myNewPoint;
		}
		g.setColor(hisColor);
		while (hisIterator.hasNext()) {
			Point2D hisNewPoint = hisIterator.next();
			drawLine(g, hisOldPoint, hisNewPoint);
			myOldPoint = hisNewPoint;
		}
    }
    
    private void drawLine(Graphics g, Point2D start, Point2D end) {
    	int x1 = (int)(start.getX() + bounds.getWidth()/2);
    	int y1 = (int)(start.getY() + bounds.getHeight()/2);
    	int x2 = (int)(end.getX() + bounds.getWidth()/2);
    	int y2 = (int)(end.getY() + bounds.getHeight()/2);    	
    	g.drawLine(x1, y1, x2, y2);
    	return;
    }

    private void drawGrid(Graphics g)
    {
    	g.setColor(Color.black);
        for (int ii = 0; ii < bounds.getWidth()/gridXResolution; ii++) 
           	g.drawLine(ii*gridXResolution + gridXResolution, 0, ii*gridXResolution + gridXResolution, (int)bounds.getHeight());
       
        for (int jj = 0; jj < bounds.getHeight()/gridYResolution; jj++) 
           	g.drawLine(0, jj*gridYResolution + gridYResolution, (int)bounds.getWidth(), jj*gridYResolution + gridYResolution);        	 
    }
    
//    private ArrayList<Point2D> transformPoints(ArrayList<Point2D> in) {
//    	ArrayList<Point2D> newPoints = new ArrayList<Point2D>();
//    	double scaleFactorX, scaleFactorY;
//    	
//    	Point2D[] extremePoints = 	{    	//get extreme points
//    								getExtremePoint(in, true, true), //point with highest x
//    								getExtremePoint(in, true, false),//point with lowest x
//    								getExtremePoint(in, false, true),//point with highest y
//    								getExtremePoint(in, false, false)//point with lowest y
//    								};
//
//    	//if the extreme points fall out of tolerance, calculate scaling factor
//    	if (extremePoints[0].getX() <= edgeTolerance) scaleFactorX = edgeTolerance - extremePoints[0].getX();
//    	else if (extremePoints[0].getX() >= dimension.width - edgeTolerance) scaleFactorX = extremePoints[0].getX() - (dimension.width - edgeTolerance);
//    	else if (extremePoints[0].getY() <= edgeTolerance) scaleFactorY = edgeTolerance - extremePoints[0].getY();
//    	else if (extremePoints[0].getY() >= dimension.height - edgeTolerance) scaleFactorY = extremePoints[0].getY() - (dimension.height - edgeTolerance);
//    	//if all extreme points are within tolerance, no transformation needed. Return original.
//    	else return in;
//    	
//    	Iterator<Point2D> iter = in.iterator();
//    	
//    	while (iter.hasNext()) {
//    		Point2D currentPoint = iter.next();
//    		double newX, newY, 
//    		oldLowX = extremePoints[1].getX(), oldLowY = extremePoints[3].getY(),
//    		newLowX = extremePoints[1].getX(), newLowY = extremePoints[3].getY(),
//    		oldUpX = extremePoints[0].getX(), oldUpY = extremePoints[2].getY(),
//    		newUpX = extremePoints[0].getX(), newUpY = extremePoints[2].getY(),
//    		oldX = currentPoint.getX(), oldY = currentPoint.getY();
//    		
//    		if (extremePoints[1].getX() <= edgeTolerance) {
//    			newLowX = edgeTolerance;
//    		}
//    		else if (extremePoints[0].getX() >= dimension.width - edgeTolerance) {
//    			newUpX = dimension.width - edgeTolerance;  			
//    		}
//    		if (extremePoints[3].getY() <= edgeTolerance) {
//    			newLowY = edgeTolerance;
//    		}
//    		else if (extremePoints[2].getY() >= dimension.height - edgeTolerance) {
//    			newUpY = dimension.height - edgeTolerance;  			
//    		}
//    		//apply linear transformation to new interval given new upper/lower bounds
//    		newX = (newLowX*(oldUpX-oldX) - newUpX*(oldLowX - oldX))/(oldUpX-oldLowX);
//    		newY = (newLowY*(oldUpY-oldY) - newUpY*(oldLowY - oldY))/(oldUpY-oldLowY);
//    		//set new location for point
//    		currentPoint.setLocation(newX, newY);
//    	}
//    	return newPoints;
//    }
    
    //this function returns an extreme point in a list of points
    //parameter x is true if the extreme point is based on x coordinate, else y.
    //parameter higher is true is extreme point is defined as highest point, else lowest.
    private Point2D getExtremePoint(ArrayList<Point2D> in, boolean x, boolean highest)
    {
    	Point2D extreme = null;
    	double extremeValue;
    	
    	if (highest) extremeValue = Double.NEGATIVE_INFINITY;
    	else extremeValue = Double.POSITIVE_INFINITY;
    	
    	Iterator<Point2D> iter = in.iterator();
    	
    	while (iter.hasNext())
    	{
    		Point2D currentPoint = iter.next();
    		
    		if (x && highest && currentPoint.getX() > extremeValue ||
    			x && !highest && currentPoint.getX() < extremeValue ||
    			!x && highest && currentPoint.getY() > extremeValue ||
    			!x && !highest && currentPoint.getY() < extremeValue) 
    		{
    			extreme = currentPoint;
    			if (x) extremeValue = extreme.getX();
    			else extremeValue = extreme.getY();
    		}
    	}
    	return extreme;
    }
    
    /**
     * Receives the coordinates for both players.
     * @param yourX Your current x coordinate
     * @param yourY Your current y coordinate
     * @param hisX The enemy's current x coordinate
     * @param hisY The enemy's current y coordinate
     */
    public void updateCoordinates(double yourX, double yourY, double hisX, double hisY) {
    	Point2D yourNewPoint = new Point2D.Double(yourX, yourY);
    	Point2D hisNewPoint = new Point2D.Double(hisX, hisY);
    	if (yourStartPoint == null) yourStartPoint = yourNewPoint;
    	if (hisStartPoint == null) hisStartPoint = hisNewPoint;
    	if (!yourPoints.contains(yourNewPoint)) yourPoints.add(yourNewPoint);
    	if (!hisPoints.contains(hisNewPoint)) hisPoints.add(hisNewPoint);
    }

    /**
     * Calculates the distance between two points.
     * @param x1 X value of point 1
     * @param y1 Y value of point 1
     * @param x2 X value of point 2
     * @param y2 Y value of point 2
     * @return The distance between the two points.
     */
    private double getDistance(int x1, int y1, int x2, int y2) 
    {  	return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));   }
}